using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Collections;
using System.Globalization;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Threading;

namespace ResourceBlender_Express {
    /// <summary>
    /// A Window to add/edit an application
    /// </summary>
    public partial class WinAddApplication : Window {
        private RBApplication application = new RBApplication();
        private ObservableSortedDictionary<string, ChangeableBundle> bundles = new ObservableSortedDictionary<string, ChangeableBundle>(new ChangeableBundle.BundleNameComparer(), new ChangeableBundle.BundleNameEqualityComparer());
        private Database db;
        private bool editing = false;

        /// <summary>
        /// Gets or sets the application being added/edited.
        /// </summary>
        /// <value>The application.</value>
        public RBApplication Application {
            get { return application; }
            set {
                application = value;
                DataContext = value;
            }
        }

        /// <summary>
        /// Gets the bundles used by the application.
        /// </summary>
        /// <value>The bundles.</value>
        public ObservableSortedDictionary<string, ChangeableBundle> Bundles {
            get { return bundles; }
        }

        /// <summary>
        /// Initializes a new <see cref="WinAddApplication"/> to add an application.
        /// </summary>
        public WinAddApplication() {
            InitializeComponent();
        }

        /// <summary>
        /// Initializes a new <see cref="WinAddApplication"/> to edit an application.
        /// </summary>
        /// <param name="application">The application.</param>
        public WinAddApplication(RBApplication application) 
            :this() {
            this.Application = application;
            Title = "Edit Application";
            editing = true;
        }

        /// <summary>
        /// Focuses the first form field and initializes the database.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void Window_Initialized(object sender, EventArgs e) {
            this.Application = new RBApplication();
            db = new Database();
            txtApplicationName.Focus();
        }

        /// <summary>
        /// Refreshes the languages and bundles displayed when the DataContext changes.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        private void Window_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) {
            if(e.NewValue is RBApplication) {
                PopulateLanguages();
                bundles.Clear();
                foreach(KeyValuePair<string, string> bundle in application.Bundles) {
                    bundles.Add(bundle.Key, new ChangeableBundle(bundle.Key, bundle.Value));
                }
                lstBundles.ItemsSource = bundles;
            }
        }

        /// <summary>
        /// Checks that all required fields are filled in and valid.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private bool ValidateApplication() {
            bool valid = true;
            txtApplicationName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
            txtDisplayName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
            if(string.IsNullOrEmpty(txtApplicationName.Text)) {
                DialogBox.Show(this, "Application name must be entered", "Error", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                txtApplicationName.Focus();
                valid = false;
            } else if(string.IsNullOrEmpty(txtDisplayName.Text)) {
                DialogBox.Show(this, "Display name must be entered", "Error", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                txtDisplayName.Focus();
                valid = false;
            } else if(txtApplicationName.Text.Length > 255) {
                DialogBox.Show(this, "Invalid application name", "Error", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                txtApplicationName.Focus();
                valid = false;
            } else if(txtDisplayName.Text.Length > 255) {
                DialogBox.Show(this, "Invalid display name", "Error", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                txtDisplayName.Focus();
                valid = false;
            } else if(lstSelectedLanguages.Items.Count == 0) {
                DialogBox.Show(this, "At least one language must be added", "Error", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                tbcLanguagesBundles.SelectedIndex = 0;
                lstSelectedLanguages.Focus();
                valid = false;
            } else if(lstBundles.Items.Count == 0) {
                DialogBox.Show(this, "At least one bundle must be added", "Error", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                tbcLanguagesBundles.SelectedIndex = 1;
                lstBundles.Focus();
                valid = false;
            }
            return valid;
        }

        /// <summary>
        /// Saves the application to the database.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void Save(object sender, ExecutedRoutedEventArgs e) {
            if(!ValidateApplication()) {
                return;
            }
            BackgroundWorker worker = new BackgroundWorker();
            WinProgress progress = new WinProgress("Saving application...", true);
            progress.Owner = this;
            worker.DoWork += (s, dwe) => {
                Database db = new Database();
                try {
                    db.OpenConnection();
                    using(Database.Command cmd = db.CreateCommand()) {
                        if(editing) {
                            cmd.CommandText = "UPDATE " + db.TablePrefix + "applications SET name = @name, displayname = @displayname, defaultlanguage = @defaultlanguage, languages = @languages WHERE id = @id";
                            cmd.AddParameterWithValue("@id", application.ID);
                            cmd.AddParameterWithValue("@name", application.ApplicationName);
                            cmd.AddParameterWithValue("@displayname", application.DisplayName);
                            cmd.AddParameterWithValue("@defaultlanguage", application.DefaultLanguage);
                            cmd.AddParameterWithValue("@languages", Utils.Implode(application.Languages, ",", false));
                            cmd.ExecuteNonQuery();

                            cmd.Parameters.Clear();

                            cmd.AddParameterWithValue("@id", string.Empty);
                            cmd.AddParameterWithValue("@name", string.Empty);
                            cmd.AddParameterWithValue("@application", application.ID);
                            foreach(KeyValuePair<string, ChangeableBundle> bundle in this.Bundles) {
                                try {
                                    switch(bundle.Value.Mode) {
                                        case ChangeableBundle.ChangeMode.Add:
                                            cmd.CommandText = string.Format("INSERT INTO {0}bundles (id, name, application) VALUES (@id, @name, @application)", db.TablePrefix);
                                            cmd.Parameters["@id"].Value = bundle.Value.ID;
                                            cmd.Parameters["@name"].Value = bundle.Value.Name;
                                            break;
                                        case ChangeableBundle.ChangeMode.Edit:
                                            cmd.CommandText = string.Format("UPDATE {0}bundles SET name = @name WHERE id = @id", db.TablePrefix);
                                            cmd.Parameters["@id"].Value = bundle.Value.ID;
                                            cmd.Parameters["@name"].Value = bundle.Value.Name;
                                            break;
                                        case ChangeableBundle.ChangeMode.Delete:
                                            cmd.CommandText = string.Format("DELETE FROM {0}bundles WHERE id = @id", db.TablePrefix);
                                            cmd.Parameters["@id"].Value = bundle.Value.ID;
                                            break;
                                        case ChangeableBundle.ChangeMode.None:
                                            continue;
                                    }
                                    cmd.ExecuteNonQuery();
                                } catch(Exception ex) {
                                    dwe.Result = "Error processing bundle: " + ex.Message;
                                }
                            }
                        } else {
                            application.ID = Utils.GenID(true);
                            cmd.CommandText = "INSERT INTO " + db.TablePrefix + "applications (id, name, displayname, defaultlanguage, languages) VALUES (@id, @name, @displayname, @defaultlanguage, @languages)";
                            cmd.AddParameterWithValue("@id", application.ID);
                            cmd.AddParameterWithValue("@name", application.ApplicationName);
                            cmd.AddParameterWithValue("@displayname", application.DisplayName);
                            cmd.AddParameterWithValue("@defaultlanguage", application.DefaultLanguage);
                            cmd.AddParameterWithValue("@languages", Utils.Implode(application.Languages, ",", false));
                            cmd.ExecuteNonQuery();

                            cmd.Parameters.Clear();

                            // no need for switch, if an application is being added, no bundles could have existed already
                            cmd.CommandText = string.Format("INSERT INTO {0}bundles (id, name, application) VALUES (@id, @name, @application)", db.TablePrefix);
                            cmd.AddParameterWithValue("@id", string.Empty);
                            cmd.AddParameterWithValue("@name", string.Empty);
                            cmd.AddParameterWithValue("@application", application.ID);
                            foreach(KeyValuePair<string, ChangeableBundle> bundle in this.Bundles) {
                                cmd.Parameters["@id"].Value = bundle.Value.ID;
                                cmd.Parameters["@name"].Value = bundle.Value.Name;
                                cmd.ExecuteNonQuery();
                            }
                        }
                    }
                } catch(Exception ex) {
                    dwe.Result = "Error #457: Error saving application - " + ex.Message;
                } finally {
                    if(db != null) {
                        try {
                            db.CloseConnection();
                        } catch { }
                    }
                }
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                progress.Close();
                if(rwe.Result != null) {
                    DialogBox.Show(this, (rwe.Result ?? string.Empty).ToString(), "Error", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                } else {
                    DialogResult = true;
                    Close();
                }
            };
            progress.Show();
            worker.RunWorkerAsync();
        }

        /// <summary>
        /// Closes the window.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void CloseWindow(object sender, ExecutedRoutedEventArgs e) {
            Close();
        }

        #region Languages
        //-------------------------------------------------------------------------------------------------------//
        /// <summary>
        /// Populates the selected/available languages ListBoxes and the default language ComboBox.
        /// </summary>
        private void PopulateLanguages() {
            Dictionary<string, string> selectedLangs = new Dictionary<string, string>();
            foreach(string selectedCode in application.Languages) {
                string friendlyName;
                try {
                    friendlyName = CultureInfo.GetCultureInfo(selectedCode).EnglishName;
                } catch {
                    friendlyName = selectedCode;
                }
                selectedLangs.Add(selectedCode, friendlyName);
            }
            var orderedSelectedNames = from k in selectedLangs.Keys
                                       orderby selectedLangs[k]
                                       select k;
            List<KeyValuePair<string, string>> sortedSelectedLangs = new List<KeyValuePair<string, string>>();
            foreach(string code in orderedSelectedNames) {
                sortedSelectedLangs.Add(new KeyValuePair<string, string>(code, selectedLangs[code]));
            }
            lstSelectedLanguages.ItemsSource = sortedSelectedLangs;
            cboDefaultLanguage.ItemsSource = sortedSelectedLangs;
            if(selectedLangs.Count > 0) {
                if(application.DefaultLanguage == null || !selectedLangs.ContainsKey(application.DefaultLanguage)) {
                    cboDefaultLanguage.SelectedIndex = 0;
                }
            }

            // add remaining languages to available langs listbox
            Dictionary<string, string> availableLangs = new Dictionary<string, string>();
            foreach(CultureInfo culture in CultureInfo.GetCultures(CultureTypes.AllCultures)) {
                if(culture.Name == "") {
                    continue;
                }
                if(!application.Languages.Contains(culture.Name)) { //selectedLangs.ContainsKey(culture.Name)) {
                    availableLangs.Add(culture.Name, culture.EnglishName);
                }
            }
            var orderedAvailableNames = from k in availableLangs.Keys
                                        orderby availableLangs[k]
                                        select k;
            List<KeyValuePair<string, string>> sortedAvailableLangs = new List<KeyValuePair<string, string>>();
            foreach(string code in orderedAvailableNames) {
                sortedAvailableLangs.Add(new KeyValuePair<string, string>(code, availableLangs[code]));
            }
            lstAvailableLanguages.ItemsSource = sortedAvailableLangs;
        }

        /// <summary>
        /// Adds the items selected in 'Available languages' to the application and refreshes.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void btnAddLanguage_Click(object sender, RoutedEventArgs e) {
            if(lstAvailableLanguages.SelectedItems == null || lstAvailableLanguages.SelectedItems.Count == 0) {
                return;
            }
            foreach(KeyValuePair<string, string> itm in lstAvailableLanguages.SelectedItems) {
                Application.Languages.Add(itm.Key);
            }
            PopulateLanguages();
        }

        /// <summary>
        /// Removes a language from the application and updates the appropriate controls.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void btnRemoveLanguage_Click(object sender, RoutedEventArgs e) {
            if(lstSelectedLanguages.SelectedItems == null || lstSelectedLanguages.SelectedItems.Count == 0) {
                return;
            }
            foreach(KeyValuePair<string, string> itm in lstSelectedLanguages.SelectedItems) {
                Application.Languages.Remove(itm.Key);
            }
            PopulateLanguages();
        }

        /// <summary>
        /// Adds a custom language code to the application and  a language from the application and updates the appropriate controls.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void btnCustomLanguage_Click(object sender, RoutedEventArgs e) {
            WinInputDialog dlg = new WinInputDialog("Add Language", "Language Code:");
            dlg.Owner = this;
            dlg.ValidateInput += () => {
                if(string.IsNullOrEmpty(dlg.UserInput)) {
                    DialogBox.Show(dlg, "Language code cannot be blank", "Invalid Language", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                    return false;
                } else if(Application.Languages.Contains(dlg.UserInput.Trim())) {
                    DialogBox.Show(dlg, string.Format("Application already contains language code {0}", dlg.UserInput), "Invalid Language", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                    return false;
                }
                return true;
            };
            if(dlg.ShowDialog() == true) {
                Application.Languages.Add(dlg.UserInput.Trim());
                PopulateLanguages();
            }
        }
        #endregion

        #region Bundles
        //-------------------------------------------------------------------------------------------------------//
        /// <summary>
        /// Adds a bundle to the application.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void AddBundle(object sender, ExecutedRoutedEventArgs e) {
            WinInputDialog dlg = new WinInputDialog("Add Bundle", "Bundle Name:");
            dlg.Owner = this;
            if(dlg.ShowDialog() == true) {
                ChangeableBundle bundle = new ChangeableBundle();
                bundle.Name = dlg.UserInput;
                bundle.Mode = ChangeableBundle.ChangeMode.Add;
                bundles.Add(bundle.ID, bundle);
            }
        }

        /// <summary>
        /// Determines whether the delete bundle command can be executed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void CanExecuteEditBundle(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = lstBundles != null && lstBundles.SelectedItem != null;
            e.Handled = true;
        }

        /// <summary>
        /// Shows a dialog to edit a bundle name.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void EditBundle(object sender, ExecutedRoutedEventArgs e) {
            if(lstBundles.SelectedItem == null) {
                return;
            }
            WinInputDialog dlg = new WinInputDialog("Edit Bundle", "Bundle Name:");
            dlg.Owner = this;
            ChangeableBundle bundle = ((KeyValuePair<string, ChangeableBundle>)lstBundles.SelectedItem).Value;
            dlg.UserInput = bundle.Name;
            if(dlg.ShowDialog() == true) {
                bundle.Name = dlg.UserInput;
                if(bundle.Mode != ChangeableBundle.ChangeMode.Add) { // if the bundle is being added, we can't update it yet because it doesn't exist in the DB.
                    bundle.Mode = ChangeableBundle.ChangeMode.Edit;
                }
                lstBundles.Items.Refresh();
            }
        }

        /// <summary>
        /// Determines whether the delete bundle command can be executed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void CanExecuteDeleteBundle(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = lstBundles != null && lstBundles.SelectedItem != null;
            e.Handled = true;
        }

        /// <summary>
        /// Confirms deletion of the selected bundle(s). Connects to the database only to check how many elements are contained by the bundles to be deleted.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void DeleteBundle(object sender, ExecutedRoutedEventArgs e) {
            if(lstBundles.SelectedItem == null) {
                return;
            }
            string message = string.Empty;
            BackgroundWorker worker = new BackgroundWorker();
            WinProgress progress = new WinProgress("Deleting bundle...", true);
            progress.Owner = this;
            worker.DoWork += (s, dwe) => {
                IList items = dwe.Argument as IList;
                try {
                    db.OpenConnection();
                    using(Database.Command cmd = db.CreateCommand()) {
                        if(items.Count == 1) {
                            KeyValuePair<string, ChangeableBundle> bundle = (KeyValuePair<string, ChangeableBundle>)items[0];
                            cmd.CommandText = string.Format("SELECT COUNT(id) FROM {0}elements WHERE bundle = @id", db.TablePrefix);
                            cmd.AddParameterWithValue("@id", bundle.Key);
                            int numElements = int.Parse(cmd.ExecuteScalar().ToString());
                            message = string.Format("Are you sure you want to delete the bundle '{0}'? This bundle contains {1} {2} which will also be deleted.", bundle.Value.Name, numElements, numElements == 1 ? "element" : "elements");
                        } else {
                            string[] bundleIds = new string[items.Count];
                            for(int i = 0; i < bundleIds.Length; i++) {
                                bundleIds[i] = ((KeyValuePair<string, ChangeableBundle>)items[i]).Key;
                            }
                            cmd.CommandText = string.Format("SELECT COUNT(id) FROM {0}elements WHERE" + db.GenerateInClause("bundle", bundleIds, false), db.TablePrefix);
                            int numElements = int.Parse(cmd.ExecuteScalar().ToString());
                            message = string.Format("Are you sure you want to delete {0} bundles? {1} {2} will also be deleted.", bundleIds.Length, numElements, numElements == 1 ? "element" : "elements");
                        }
                    }
                    dwe.Result = message;
                } catch(Exception ex) {
                    dwe.Result = "Error #452: Could not connect to database - " + ex.Message;
                } finally {
                    try {
                        db.CloseConnection();
                    } catch { }
                }
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                progress.Close();
                if(message == null) {
                    DialogBox.Show(this, rwe.Result.ToString(), "Database Error", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                } else if(DialogBox.Show(message, "Delete Bundles", DialogBox.DialogBoxType.YesNo, DialogBox.DialogBoxIcon.Question) == DialogBox.DialogBoxButton.Yes) {
                    for(int i=0;i<lstBundles.SelectedItems.Count;i++) {
                        KeyValuePair<string, ChangeableBundle> bundle = (KeyValuePair<string, ChangeableBundle>)lstBundles.SelectedItems[i];
                        if(bundle.Value.Mode == ChangeableBundle.ChangeMode.Add) { // the bundle hasn't been saved to the DB yet, just delete it
                            bundles.Remove(bundle.Key);
                        } else {
                            bundles[bundle.Key].Mode = ChangeableBundle.ChangeMode.Delete;
                        }
                    }
                }
                lstBundles.Items.Refresh();
            };
            progress.Show();
            worker.RunWorkerAsync(lstBundles.SelectedItems);
        }
        #endregion
    }
    
}

